import time
import datetime
import tkinter as tk
from tkinter import messagebox, ttk, simpledialog
import sys
import ctypes
import os
import hashlib
import threading
import winreg  # 用于Windows开机启动设置
from cryptography.fernet import Fernet  # 用于加密解密

class LicenseManager:
    """注册码管理系统，支持指定具体日期生成注册码"""
    
    # 固定密钥（生成和验证必须一致）
    SECRET_KEY = "final_secure_key_2024"
    
    @staticmethod
    def generate_registration_code(specific_date):
        """生成注册码，基于指定的具体日期"""
        try:
            year = specific_date.strftime("%Y")
            month = specific_date.strftime("%m")  # 确保两位数
            day = specific_date.strftime("%d")    # 确保两位数
            
            # 生成哈希验证部分
            date_str = f"{year}{month}{day}"
            hash_raw = hashlib.md5(f"{date_str}_{LicenseManager.SECRET_KEY}".encode("utf-8")).hexdigest()
            hash_part = hash_raw[:8].upper()  # 取前8位作为验证
            
            # 组装注册码（严格格式：YYYY-MM-DD-XXXX-XXXX）
            reg_code = f"{year}-{month}-{day}-{hash_part[:4]}-{hash_part[4:8]}"
            return reg_code, specific_date
        except Exception as e:
            print(f"生成注册码错误: {str(e)}")
            return None, None
    
    @staticmethod
    def verify_registration_code(reg_code):
        """验证注册码"""
        try:
            code = reg_code.strip()  # 去除前后空格
            
            # 检查长度：YYYY-MM-DD-XXXX-XXXX 共17个字符
            if len(code) != 20:
                print(f"注册码长度错误：实际{len(code)}位，应为20位")
                return None
            
            # 检查分隔符位置
            if code[4] != "-" or code[7] != "-" or code[10] != "-" or code[15] != "-":
                print(f"注册码分隔符位置错误")
                return None
            
            # 提取各部分并验证
            parts = code.split("-")
            if len(parts) != 5:
                print(f"注册码分割后部分错误：实际{len(parts)}部分")
                return None
            
            year_part, month_part, day_part, hash1, hash2 = parts
            
            # 验证年份（4位数字）
            if not year_part.isdigit() or len(year_part) != 4:
                print(f"年份格式错误：{year_part}")
                return None
            
            # 验证月份（2位数字，1-12）
            if not month_part.isdigit() or len(month_part) != 2:
                print(f"月份格式错误：{month_part}")
                return None
            month = int(month_part)
            if month < 1 or month > 12:
                print(f"月份数值错误：{month}")
                return None
            
            # 验证日期（2位数字，合法范围）
            if not day_part.isdigit() or len(day_part) != 2:
                print(f"日期格式错误：{day_part}")
                return None
            
            # 验证日期合法性
            try:
                expire_date = datetime.datetime(int(year_part), int(month_part), int(day_part))
            except ValueError as e:
                print(f"日期数值错误：{year_part}-{month_part}-{day_part}（{str(e)}）")
                return None
            
            # 验证哈希部分
            hash_part = f"{hash1}{hash2}"
            if not hash_part.isalnum() or len(hash_part) != 8:
                print(f"哈希部分格式错误：{hash_part}")
                return None
            
            # 验证哈希合法性
            date_str = f"{year_part}{month_part}{day_part}"
            computed_hash = hashlib.md5(f"{date_str}_{LicenseManager.SECRET_KEY}".encode("utf-8")).hexdigest()[:8].upper()
            
            if hash_part == computed_hash:
                print(f"注册码验证成功！有效期：{expire_date.strftime('%Y-%m-%d')}")
                return expire_date
            else:
                print(f"哈希验证失败：实际{hash_part}，预期{computed_hash}")
                return None
                
        except Exception as e:
            print(f"验证过程异常：{str(e)}")
            return None


class ExpirationChecker:
    # 默认到期时间设置（年, 月, 日）- 可在此修改默认值
    DEFAULT_EXPIRE_DATE = (2025, 10, 11)  # 示例：2024年12月31日
    
    # 加密相关配置
    _ENCRYPTION_KEY = b'tPn1bhazYoKYUEfC4x3p6syAbU47gJ_4wZjuEOZab5Q='  # 32字节的Base64编码密钥
    _DATE_FILE = "exp_date.enc"  # 加密的日期文件
    _SALT = b'fixed_salt_for_licensing'  # 盐值，增强加密安全性
    
    def __init__(self):
        # 初始化加密器
        self._cipher = Fernet(self._ENCRYPTION_KEY)
        
        # 设置默认到期日期
        default_expire = datetime.datetime(*self.DEFAULT_EXPIRE_DATE)
        # 如果默认日期已过期，则设置为昨天
        if default_expire < datetime.datetime.now():
            default_expire = datetime.datetime.now() - datetime.timedelta(days=1)
            
        self.expiration_date = self.load_expiration_date() or default_expire
        self.running = True
        self.root = None
        self.force_window_open = False  # 控制窗口是否可关闭
        self.reminder_shown = False  # 标记到期前提醒是否已显示
        
        # 设置开机启动
        self.set_startup()
        
    def set_startup(self):
        """设置程序开机启动（仅Windows系统）"""
        try:
            if getattr(sys, 'frozen', False):
                # 打包后的可执行文件路径
                exe_path = sys.executable
            else:
                # 脚本运行路径
                exe_path = os.path.abspath(sys.argv[0])
                # 如果是Python脚本，需要用python.exe启动
                exe_path = f'"{sys.executable}" "{exe_path}"'
            
            # 注册表项路径
            key_path = r"Software\Microsoft\Windows\CurrentVersion\Run"
            
            # 打开注册表
            key = winreg.OpenKey(
                winreg.HKEY_CURRENT_USER,
                key_path,
                0,
                winreg.KEY_SET_VALUE
            )
            
            # 设置开机启动项
            winreg.SetValueEx(
                key,
                "ExpirationChecker",  # 启动项名称
                0,
                winreg.REG_SZ,
                exe_path
            )
            
            winreg.CloseKey(key)
            print("开机启动设置成功")
        except Exception as e:
            print(f"开机启动设置失败：{str(e)}")
            print("可能需要管理员权限")
    
    def save_expiration_date(self):
        """加密保存到期日期到本地文件"""
        try:
            app_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
            date_file = os.path.join(app_dir, self._DATE_FILE)
            
            # 转换日期为字符串
            date_str = self.expiration_date.strftime("%Y-%m-%d")
            
            # 生成哈希值用于验证文件完整性
            date_hash = hashlib.sha256(f"{date_str}{self._SALT.decode()}".encode()).hexdigest()
            
            # 组合数据：日期|哈希
            data_to_encrypt = f"{date_str}|{date_hash}".encode()
            
            # 加密数据
            encrypted_data = self._cipher.encrypt(data_to_encrypt)
            
            # 保存加密后的数据
            with open(date_file, "wb") as f:
                f.write(encrypted_data)
                
            return True
        except Exception as e:
            print(f"保存到期日期失败：{str(e)}")
            return False
    
    def load_expiration_date(self):
        """从加密文件加载并验证到期日期"""
        try:
            app_dir = os.path.dirname(os.path.abspath(sys.argv[0]))
            date_file = os.path.join(app_dir, self._DATE_FILE)
            
            if not os.path.exists(date_file):
                return None
            
            # 读取加密数据
            with open(date_file, "rb") as f:
                encrypted_data = f.read()
            
            # 解密数据
            try:
                decrypted_data = self._cipher.decrypt(encrypted_data).decode()
            except:
                print("解密失败，文件可能被篡改或损坏")
                return None
            
            # 拆分日期和哈希
            parts = decrypted_data.split("|")
            if len(parts) != 2:
                print("解密后的数据格式错误")
                return None
                
            date_str, stored_hash = parts
            
            # 验证哈希
            computed_hash = hashlib.sha256(f"{date_str}{self._SALT.decode()}".encode()).hexdigest()
            if stored_hash != computed_hash:
                print("哈希验证失败，文件可能被篡改")
                return None
            
            # 解析日期
            return datetime.datetime.strptime(date_str, "%Y-%m-%d")
            
        except Exception as e:
            print(f"加载到期日期失败：{str(e)}")
            return None
    
    @staticmethod
    def generate_encryption_key():
        """生成新的加密密钥（仅用于开发）"""
        return Fernet.generate_key()
    
    def check_expiration_status(self):
        """检查到期状态，返回状态和剩余天数"""
        now = datetime.datetime.now()
        time_left = self.expiration_date - now
        days_left = time_left.total_seconds() / (24 * 3600)  # 转换为天数
        
        if days_left < 0:
            return "expired", abs(days_left)  # 已过期
        elif days_left <= 3:
            return "soon", days_left  # 即将过期（3天内）
        else:
            return "valid", days_left  # 正常
        
    def show_reminder_window(self):
        """显示到期前提醒窗口（可关闭）"""
        if not self.root:
            self.root = tk.Tk()
            self.root.withdraw()
        
        # 计算剩余天数
        status, days = self.check_expiration_status()
        days_str = f"{days:.1f}" if days < 1 else f"{int(days)}"
        
        reminder_window = tk.Toplevel(self.root)
        reminder_window.title("软件即将到期提醒")
        reminder_window.geometry("400x200")
        reminder_window.resizable(False, False)
        reminder_window.attributes("-topmost", True)
        
        # 窗口居中
        reminder_window.update_idletasks()
        width = reminder_window.winfo_width()
        height = reminder_window.winfo_height()
        x = (reminder_window.winfo_screenwidth() // 2) - (width // 2)
        y = (reminder_window.winfo_screenheight() // 2) - (height // 2)
        reminder_window.geometry(f"{width}x{height}+{x}+{y}")
        
        # 提醒信息
        ttk.Label(
            reminder_window, 
            text=f"⚠️ 您的软件将在 {days_str} 天后到期！",
            font=("Arial", 12, "bold"),
            foreground="#e67e22"
        ).pack(pady=20)
        
        ttk.Label(
            reminder_window, 
            text=f"当前到期日期：{self.expiration_date.strftime('%Y年%m月%d日')}",
            font=("Arial", 10)
        ).pack(pady=5)
        
        # 按钮
        btn_frame = ttk.Frame(reminder_window)
        btn_frame.pack(pady=20)
        
        def register_now():
            reminder_window.destroy()
            self.show_registration_window()
            
        def remind_later():
            reminder_window.destroy()
            self.reminder_shown = False  # 允许下次再提醒
        
        ttk.Button(btn_frame, text="立即注册", command=register_now).pack(side=tk.LEFT, padx=10)
        ttk.Button(btn_frame, text="稍后提醒", command=remind_later).pack(side=tk.LEFT, padx=10)
        
        self.reminder_shown = True
        reminder_window.wait_window()
    
    def show_registration_window(self):
        """显示注册窗口（过期状态下不可关闭和最小化）"""
        if not self.root:
            self.root = tk.Tk()
            self.root.withdraw()
        
        reg_window = tk.Toplevel(self.root)
        reg_window.title("软件注册")
        reg_window.geometry("450x300")
        reg_window.resizable(False, False)
        reg_window.attributes("-topmost", True)
        
        # 获取状态
        status, _ = self.check_expiration_status()
        
        # 如果已过期，设置窗口不可关闭和最小化
        if status == "expired":
            self.force_window_open = True
            
            # 重写关闭事件
            def on_close():
                messagebox.showwarning("无法关闭", "软件已过期，请注册后继续使用！")
            reg_window.protocol("WM_DELETE_WINDOW", on_close)
            
            # 移除最小化按钮（Windows系统）
            if sys.platform.startswith('win'):
                hwnd = ctypes.windll.user32.GetParent(reg_window.winfo_id())
                style = ctypes.windll.user32.GetWindowLongW(hwnd, -16)  # GWL_STYLE
                # 移除WS_MINIMIZEBOX样式
                style &= ~0x00020000
                ctypes.windll.user32.SetWindowLongW(hwnd, -16, style)
                reg_window.update_idletasks()
        else:
            self.force_window_open = False
            reg_window.protocol("WM_DELETE_WINDOW", reg_window.destroy)
        
        # 窗口居中
        reg_window.update_idletasks()
        width = reg_window.winfo_width()
        height = reg_window.winfo_height()
        x = (reg_window.winfo_screenwidth() // 2) - (width // 2)
        y = (reg_window.winfo_screenheight() // 2) - (height // 2)
        reg_window.geometry(f"{width}x{height}+{x}+{y}")
        
        # 注册码格式提示
        ttk.Label(
            reg_window, 
            text="注册码格式：YYYY-MM-DD-XXXX-XXXX", 
            font=("Arial", 9), 
            foreground="gray"
        ).pack(pady=5)
        
        # 注册码输入框
        ttk.Label(reg_window, text="请输入注册码：").pack(pady=10)
        code_var = tk.StringVar()
        code_entry = ttk.Entry(
            reg_window, 
            textvariable=code_var, 
            font=("Arial", 12), 
            width=35
        )
        code_entry.pack(pady=5)
        
        # 状态提示
        status_var = tk.StringVar(value="请输入符合格式的注册码")
        status_label = ttk.Label(
            reg_window, 
            textvariable=status_var, 
            font=("Arial", 10)
        )
        status_label.pack(pady=10)
        
        def verify_code():
            """验证按钮点击事件"""
            code = code_var.get()
            if not code:
                status_var.set("❌ 请输入注册码")
                status_label.config(foreground="red")
                return
            
            status_var.set("🔍 正在验证...")
            status_label.config(foreground="blue")
            reg_window.update()
            
            # 执行验证
            result = LicenseManager.verify_registration_code(code)
            
            if result:
                # 验证成功
                self.expiration_date = result
                self.save_expiration_date()
                status_var.set(f"✅ 注册成功！有效期至：{result.strftime('%Y年%m月%d日')}")
                status_label.config(foreground="green")
                self.force_window_open = False  # 允许关闭窗口
                
                # 恢复窗口样式（如果是Windows系统）
                if sys.platform.startswith('win') and status == "expired":
                    hwnd = ctypes.windll.user32.GetParent(reg_window.winfo_id())
                    style = ctypes.windll.user32.GetWindowLongW(hwnd, -16)
                    style |= 0x00020000  # 添加WS_MINIMIZEBOX样式
                    ctypes.windll.user32.SetWindowLongW(hwnd, -16, style)
                
                reg_window.after(2000, reg_window.destroy)
            else:
                # 验证失败
                status_var.set("❌ 注册码无效，请重新输入")
                status_label.config(foreground="red")
                # 如果已过期，保持窗口强制打开
                if status == "expired":
                    self.force_window_open = True
        
        # 按钮区域
        btn_frame = ttk.Frame(reg_window)
        btn_frame.pack(pady=15)
        
        verify_btn = ttk.Button(btn_frame, text="验证", command=verify_code)
        verify_btn.pack(side=tk.LEFT, padx=20)
        
        # 只有未过期时才显示取消按钮
        if status != "expired":
            cancel_btn = ttk.Button(btn_frame, text="取消", command=reg_window.destroy)
            cancel_btn.pack(side=tk.LEFT, padx=20)
        
        reg_window.wait_window()
    
    def show_expiration_message(self):
        """显示软件到期提示（不可关闭和最小化）"""
        if not self.root:
            self.root = tk.Tk()
            self.root.withdraw()
        
        # 创建强制弹窗
        expire_window = tk.Toplevel(self.root)
        expire_window.title("软件已到期")
        expire_window.geometry("400x200")
        expire_window.resizable(False, False)
        expire_window.attributes("-topmost", True)
        self.force_window_open = True
        
        # 禁止关闭窗口
        def on_close():
            messagebox.showwarning("无法关闭", "软件已过期，请注册后继续使用！")
        expire_window.protocol("WM_DELETE_WINDOW", on_close)
        
        # 移除最小化按钮（Windows系统）
        if sys.platform.startswith('win'):
            hwnd = ctypes.windll.user32.GetParent(expire_window.winfo_id())
            style = ctypes.windll.user32.GetWindowLongW(hwnd, -16)  # GWL_STYLE
            style &= ~0x00020000  # 移除WS_MINIMIZEBOX样式
            ctypes.windll.user32.SetWindowLongW(hwnd, -16, style)
            expire_window.update_idletasks()
        
        # 窗口居中
        expire_window.update_idletasks()
        width = expire_window.winfo_width()
        height = expire_window.winfo_height()
        x = (expire_window.winfo_screenwidth() // 2) - (width // 2)
        y = (expire_window.winfo_screenheight() // 2) - (height // 2)
        expire_window.geometry(f"{width}x{height}+{x}+{y}")
        
        # 到期信息
        ttk.Label(
            expire_window, 
            text="❌ 您的软件已到期！",
            font=("Arial", 12, "bold"),
            foreground="#e74c3c"
        ).pack(pady=20)
        
        ttk.Label(
            expire_window, 
            text="请注册以继续使用软件功能",
            font=("Arial", 10)
        ).pack(pady=5)
        
        # 注册按钮
        def go_register():
            # 恢复窗口样式
            if sys.platform.startswith('win'):
                hwnd = ctypes.windll.user32.GetParent(expire_window.winfo_id())
                style = ctypes.windll.user32.GetWindowLongW(hwnd, -16)
                style |= 0x00020000  # 添加WS_MINIMIZEBOX样式
                ctypes.windll.user32.SetWindowLongW(hwnd, -16, style)
            
            expire_window.destroy()
            self.show_registration_window()
        
        ttk.Button(expire_window, text="立即注册", command=go_register).pack(pady=15)
        
        expire_window.wait_window()
    
    def run(self):
        """启动程序主逻辑"""
        self.save_expiration_date()
        self.root = tk.Tk()
        self.root.withdraw()
        
        # 启动后台检查线程
        check_thread = threading.Thread(target=self.background_check, daemon=True)
        check_thread.start()
        
        try:
            self.root.mainloop()
        except KeyboardInterrupt:
            print("程序被手动中断")
            self.running = False
    
    def background_check(self):
        """后台定期检查到期状态"""
        while self.running:
            status, _ = self.check_expiration_status()
            
            # 根据状态显示不同窗口
            if status == "expired":
                # 已过期 - 强制弹窗，关闭后10分钟再检查
                self.root.after(0, self.show_expiration_message)
                time.sleep(600)  # 10分钟后再次检查
            elif status == "soon" and not self.reminder_shown:
                # 即将过期（3天内）- 显示可关闭的提醒
                self.root.after(0, self.show_reminder_window)
                time.sleep(3600)  # 1小时后再次检查
            else:
                # 正常状态 - 5分钟检查一次
                time.sleep(300)


def input_date_and_generate_code():
    """让用户输入具体日期并生成注册码"""
    try:
        # 创建临时Tkinter窗口用于输入
        root = tk.Tk()
        root.withdraw()  # 隐藏主窗口
        
        # 获取当前日期作为默认值
        today = datetime.datetime.now()
        default_year = today.year
        default_month = today.month
        default_day = today.day
        
        # 输入年份
        year = simpledialog.askinteger(
            "输入年份", 
            "请输入到期年份（如2025）：",
            minvalue=2000, 
            maxvalue=2100,
            initialvalue=default_year + 1
        )
        if not year:
            print("取消输入")
            return None
        
        # 输入月份
        month = simpledialog.askinteger(
            "输入月份", 
            "请输入到期月份（1-12）：",
            minvalue=1, 
            maxvalue=12,
            initialvalue=default_month
        )
        if not month:
            print("取消输入")
            return None
        
        # 确定当月最大天数
        if month in [4, 6, 9, 11]:
            max_day = 30
        elif month == 2:
            # 处理闰年
            if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):
                max_day = 29
            else:
                max_day = 28
        else:
            max_day = 31
        
        # 输入日期
        day = simpledialog.askinteger(
            "输入日期", 
            f"请输入到期日期（1-{max_day}）：",
            minvalue=1, 
            maxvalue=max_day,
            initialvalue=default_day if default_day <= max_day else 1
        )
        if not day:
            print("取消输入")
            return None
        
        # 创建日期对象
        specific_date = datetime.datetime(year, month, day)
        
        # 检查日期是否在今天之后
        if specific_date <= datetime.datetime.now():
            messagebox.showerror("日期无效", "到期日期必须在今天之后！")
            return None
            
        # 生成注册码
        root.destroy()  # 关闭临时窗口
        return LicenseManager.generate_registration_code(specific_date)
        
    except Exception as e:
        print(f"输入日期时出错：{str(e)}")
        return None, None


def generate_test_reg_code():
    """生成测试用注册码（通过输入具体日期）"""
    code, expire_date = input_date_and_generate_code()
    if code and expire_date:
        print("\n" + "="*50)
        print("📌 测试注册码生成成功")
        print(f"有效期至：{expire_date.strftime('%Y年%m月%d日')}")
        print(f"注册码：{code}")
        print("="*50 + "\n")
    return code


# 生成新的加密密钥（仅开发时使用）
def generate_new_key():
    new_key = Fernet.generate_key()
    print(f"新生成的加密密钥：{new_key.decode()}")
    print("请替换ExpirationChecker类中的_ENCRYPTION_KEY变量")


if __name__ == "__main__":
    # 生成测试注册码（通过输入具体日期）
    # generate_test_reg_code()
    
    # 启动程序
    checker = ExpirationChecker()
    print(f"🚀 程序启动成功，当前到期日期：{checker.expiration_date.strftime('%Y年%m月%d日')}")
    print("ℹ️ 按Ctrl+C可退出程序")
    
    # 隐藏控制台窗口（仅在打包为exe时生效）
    if getattr(sys, 'frozen', False):
        ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 0)
    
    checker.run()
    